import {describeConfig, generateDeployRequest, getUpdateStrategy} from '../src/deployUtils'
import { IParams } from '../src/params';
import { SaeInterface } from '../src/sae';
import * as sinon from 'sinon';
import { expect } from 'chai';
import * as step from '@flow-step/step-toolkit';
import {getDateVersion, printJSON} from '../src/utils'
import {
    DescribeApplicationConfigRequest, DescribeApplicationConfigResponse
} from '@alicloud/sae20190506'

const params: IParams = {
    APPLICATION_ID: 'app123',
    Batch: '10',
    BatchWaitTime: '60',
    ReleaseType: 'manual',
    StrategyType: 'BatchUpdate',
    USE_ACR_EE: false,
    buildJobID: 0,
    domestic: false,
    pipelineID: 0,
    pipelineName: '',
    projectDir: '',
    ARTIFACT: 'artifact.jar',
    regionID: 'cn-hangzhou',
    workSpace: '',
    CHANGE_ORDER_ID: '12345',
    APP_NAME: 'my-app',
    PACKAGE: '{"ARTIFACT_URL": "test-url", "ARTIFACT_NAME": "artifact.jar", "WEB_DOWNLOAD_URL": "test", "artifact": "application.jar", "type": "flowPublic","downloadUrl": "test-url"}',
    GrayNumber: '10',
    minReadyInstances: '2',
    minReadyInstanceRatio: '50',
    minReadyInstanceType: 'byNumber',
    waitTimeInBatch: '30',
    enableGreyTagRoute: 'true'
};


describe('describeConfig', () => {

    let printJSONStub: sinon.SinonStub;
    let mockSae: SaeInterface;
    let sandbox: sinon.SinonSandbox;
    let describeApplicationConfigStub: sinon.SinonStub;
    let infoStub: sinon.SinonStub;

    beforeEach(() => {
        sandbox = sinon.createSandbox();
        describeApplicationConfigStub = sandbox.stub()
        mockSae = {
            describeApplicationConfig: describeApplicationConfigStub
        } as unknown as SaeInterface;

        infoStub = sandbox.stub(step, 'info');
        printJSONStub = sandbox.stub(require('../src/utils'), 'printJSON');
    });

    afterEach(() => {
        sandbox.restore();
    });


    it('should handle successful application config retrieval', async () => {
        const mockResponse = new DescribeApplicationConfigResponse({
            body: {
                code: '200',
                data: {
                    appName: 'my-app',
                    packageType: 'jar'
                }
            }
        });

        describeApplicationConfigStub.resolves(mockResponse);

        const result = await describeConfig(params, mockSae);

        expect(describeApplicationConfigStub.calledOnce).to.be.true;
        expect(describeApplicationConfigStub.firstCall.args[0]).to.deep.equal(new DescribeApplicationConfigRequest({ appId: 'app123' }));
        expect(infoStub.calledWith('[USER] Application Config')).to.be.true;
        expect(printJSONStub.calledWith(mockResponse.body?.data)).to.be.true;
        expect(infoStub.calledWith('[USER]APPLICATION_ID=app123')).to.be.true;
        expect(infoStub.calledWith('[USER]ARTIFACT=artifact.jar')).to.be.true;
        expect(infoStub.calledWith('[USER]PACKAGE={"ARTIFACT_URL": "test-url", "ARTIFACT_NAME": "artifact.jar", "WEB_DOWNLOAD_URL": "test", "artifact": "application.jar", "type": "flowPublic","downloadUrl": "test-url"}')).to.be.true;
        expect(infoStub.calledWith('[USER]APP_NAME=my-app')).to.be.true;
        expect(infoStub.calledWith('[USER]Package_Type=jar')).to.be.true;
        expect(infoStub.calledWith('[USER]StrategyType=BatchUpdate')).to.be.true;
        expect(infoStub.calledWith('[USER]GrayNumber=10')).to.be.true;
        expect(infoStub.calledWith('[USER]minReadyInstances=2')).to.be.true;
        expect(infoStub.calledWith('[USER]minReadyInstanceRatio=50')).to.be.true;
        expect(infoStub.calledWith('[USER]Batch=10')).to.be.true;
        expect(infoStub.calledWith('[USER]BatchWaitTime=60')).to.be.true;
        expect(infoStub.calledWith('[USER]ReleaseType=manual')).to.be.true;
        expect(result).to.deep.equal(mockResponse.body?.data);
    });


    it('should handle failed application config retrieval', async () => {
        const error = new Error('Some error occurred');
        describeApplicationConfigStub.rejects(error);

        try {
            await describeConfig(params, mockSae);
            expect.fail('Expected describeConfig to throw an error');
        } catch (e: any) {
            expect(e.message).to.equal(`failed to describeApplicationConfig: ${error}`);
        }
    });

    it('should handle invalid response from describeApplicationConfig', async () => {
        const mockResponse = new DescribeApplicationConfigResponse({
            body: {
                code: '500',
                data: null
            }
        });

        describeApplicationConfigStub.resolves(mockResponse);

        try {
            await describeConfig(params, mockSae);
            expect.fail('Expected describeConfig to throw an error');
        } catch (e: any) {
            expect(e.message).to.equal('failed to get application config, please check the SAE console');
        }
    });

    it('should handle missing appName or packageType in response', async () => {
        const mockResponse = new DescribeApplicationConfigResponse({
            body: {
                code: '200',
                data: {
                    appName: undefined,
                    packageType: undefined
                }
            }
        });

        describeApplicationConfigStub.resolves(mockResponse);

        try {
            await describeConfig(params, mockSae);
            expect.fail('Expected describeConfig to throw an error');
        } catch (e: any) {
            expect(e.message).to.equal('appName or packageType is not found, please check the application config');
        }
    });
});

describe('generateDeployRequest', () => {
    let sandbox: sinon.SinonSandbox;
    let infoStub: sinon.SinonStub;
    let getDateVersionStub: sinon.SinonStub;

    beforeEach(() => {
        sandbox = sinon.createSandbox();
        infoStub = sandbox.stub(step, 'info');
        getDateVersionStub = sandbox.stub(require('../src/utils'), 'getDateVersion');
    })

    afterEach(() => {
        sandbox.restore();
    });

    it('should generate a valid deploy request for a jar package', () => {
        getDateVersionStub.returns('DATE')
        const deployRequest = generateDeployRequest(params, 'jar');

        expect(infoStub.calledWith('[USER]ARTIFACT_URL=test-url')).to.be.true;
        expect(infoStub.calledWith('[USER]ARTIFACT_NAME=artifact.jar')).to.be.true;
        expect(infoStub.calledWith('[USER]PACKAGE_VERSION=DATE')).to.be.true;
        expect(infoStub.calledWith('THE DEPLOYMENT TASK BEGINS')).to.be.true;
        expect(infoStub.calledWith('Start upgrading application version to: test-url')).to.be.true;

        expect(deployRequest.appId).to.equal('app123');
        expect(deployRequest.packageVersion).to.equal("DATE");
        expect(deployRequest.updateStrategy).to.equal(JSON.stringify({
            type: 'BatchUpdate',
            batchUpdate: {
                batch: '10',
                releaseType: 'manual',
                batchWaitTime: '60'
            }
        }));
        expect(deployRequest.batchWaitTime).to.equal(30);
        expect(deployRequest.enableGreyTagRoute).to.be.true;
        expect(deployRequest.packageUrl).to.equal('test-url');
        expect(deployRequest.minReadyInstances).to.equal(2);
    });

    it('should generate a valid deploy request for a docker image', () => {
        getDateVersionStub.returns('DATE')
        const paramsWithDockerImage = {
            ...params,
            PACKAGE: "{\"type\":\"docker_image\",\"artifact\":\"DOCKER_OUTPUT\",\"DOCKER_URL\":\"registry.cn-hangzhou.aliyuncs.com/t/t:1\",\"dockerUrl\":\"registry.cn-hangzhou.aliyuncs.com/t/t:1\",\"dockerTag\":\"1\"}"
        };
        const deployRequest = generateDeployRequest(paramsWithDockerImage, 'Image');

        expect(infoStub.calledWith('[USER]IMAGE_URL=registry.cn-hangzhou.aliyuncs.com/t/t:1')).to.be.true;
        expect(infoStub.calledWith('[USER]PACKAGE_VERSION=DATE')).to.be.true;
        expect(infoStub.calledWith('THE DEPLOYMENT TASK BEGINS')).to.be.true;
        expect(infoStub.calledWith('Start upgrading application version to: registry.cn-hangzhou.aliyuncs.com/t/t:1')).to.be.true;

        expect(deployRequest.appId).to.equal('app123');
        expect(deployRequest.packageVersion).to.equal('DATE');
        expect(deployRequest.updateStrategy).to.equal(JSON.stringify({
            type: 'BatchUpdate',
            batchUpdate: {
                batch: '10',
                releaseType: 'manual',
                batchWaitTime: '60'
            }
        }));
        expect(deployRequest.batchWaitTime).to.equal(30);
        expect(deployRequest.enableGreyTagRoute).to.be.true;
        expect(deployRequest.imageUrl).to.equal('registry.cn-hangzhou.aliyuncs.com/t/t:1');
        expect(deployRequest.minReadyInstances).to.equal(2);
    });

    it('should handle invalid minReadyInstanceType', () => {
        params.minReadyInstanceType = 'invalidType';

        try {
            generateDeployRequest(params, 'jar');
            expect.fail('Expected generateDeployRequest to throw an error');
        } catch (error: any) {
            expect(error.message).to.equal('minReadyInstanceType is not valid');
        }
    });
});



describe('getUpdateStrategy', () => {
    it('should return correct strategy for BatchUpdate', () => {
        params.StrategyType = 'BatchUpdate';
        const result = getUpdateStrategy(params);
        expect(result).to.equal(
            JSON.stringify({
                type: 'BatchUpdate',
                batchUpdate: {
                    batch: '10',
                    releaseType: 'manual',
                    batchWaitTime: '60'
                }
            })
        );
    });

    it('should return correct strategy for GrayBatchUpdate', () => {

        params.StrategyType = 'GrayBatchUpdate';
        params.GrayNumber = '5';

        const result = getUpdateStrategy(params);
        expect(result).to.equal(
            JSON.stringify({
                type: 'GrayBatchUpdate',
                batchUpdate: {
                    batch: '10',
                    releaseType: 'manual',
                    batchWaitTime: '60'
                },
                grayUpdate: {
                    gray: '5'
                }
            })
        );
    });

    it('should throw an error for unknown StrategyType', () => {
        console.log(params)
        params.StrategyType = 'UnknownStrategy';
        expect(() => getUpdateStrategy(params)).to.throw('missing deployStrategy');
    });
});
